package com.xxrl.hmwork.service;

import com.xxrl.hmwork.commons.MailUtils;
import com.xxrl.hmwork.commons.StringUtils;
import com.xxrl.hmwork.conf.MyConfig;
import com.xxrl.hmwork.vo.FileItem;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * @author mis
 */
@Service
public class FileService {
    private Logger logger = LoggerFactory.getLogger(FileService.class);

    private String uploadDirectory;

    private String cloudDirectory;

    private final MailUtils mailUtils;

    // 拷贝文件时 一次操作的大小
    private static final int IO_SIZE = 1024*10;

    public FileService(@NotNull MyConfig config, MailUtils mailUtils) {
        this.mailUtils = mailUtils;
        uploadDirectory = config.getUploadDir();
        cloudDirectory = config.getCloudDir();
        logger.info("Upload directory is {}, Cloud directory is {}", uploadDirectory, cloudDirectory);
    }

    /**
     * 上传文件夹中文件数量
     * @return 文件数
     */
    public int countFile() {
        File dir = uploadDir();
        return dir.list() != null ?
                Objects.requireNonNull(dir.list()).length: 0;
    }

    /**
     * 列出文件夹中文件的信息
     * @param files 文件数组
     * @param page 页号 从第一页开始
     * @param limit 分页长度
     * @return 封装成FileItem返回
     */
    @NotNull
    private List<FileItem> listFile(File[] files, Integer page, Integer limit) {
        List<FileItem> fileItems = new ArrayList<>();

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (files == null) {
            return fileItems;
        }
        for (int i = limit*(page-1); i< limit*page && i< files.length; ++i) {
            if (files[i] == null) {
                continue;
            }
            FileItem fileItem = new FileItem();
            fileItem.setId(i+1);
            fileItem.setName(files[i].getName());
            fileItem.setModified(sdf.format(new Date(files[i].lastModified())));
            fileItem.setStatus("published");
            fileItem.setDir(files[i].isDirectory());
            fileItem.setType(getFileType(files[i]));
            fileItem.setSize(files[i].length());
            // getPath会返回构造时的路径参数
            fileItem.setUrl(files[i].getPath());
            fileItems.add(fileItem);
        }
        return fileItems;
    }


    /**
     * 查询前缀和名称中包含的 文件信息
     * 用filter查询出file数组后 调用到私有方法
     * @param page 页号 从第一页开始
     * @param limit 分页长度
     * @param title 名称
     * @param type 前缀
     * @return 包装好的信息
     */
    public List<FileItem> listFile(Integer page, Integer limit, String title, String type) {
        FilenameFilter filter = (File dir, String name) ->
                !StringUtils.isEmpty(name) && name.contains(title) && name.startsWith(type);

        File[] files = uploadDir().listFiles(filter);
        return listFile(files,page,limit);
    }

    /**
     * 列出用户云文件夹的文件
     * @param user 用户
     * @return 封装好的数据
     */
    public List<FileItem> listCloudFile(String user) {
        File dir = cloudDir("/"+user);
        return listFile(dir.listFiles(),1,20);
    }

    public void deleteCloudFile(String url) {
        File file = new File(cloudDirectory + url);
        System.out.println(file.length());
        // 这个不删？？？
//        file.deleteOnExit();
        file.delete();
    }

    /**
     * 上传云文件
     * 参数如果为空 则不会上传
     * @param user 用户名
     * @param file 文件
     */
    public void uploadCloudFile(String user, MultipartFile file) {

        if (file == null || file.isEmpty() ||
                StringUtils.isEmpty(file.getOriginalFilename()) ||
                StringUtils.isEmpty(user)) {
            return ;
        }
        try (InputStream is = file.getInputStream();
             OutputStream os = new FileOutputStream(new File(cloudDir("/"+user), file.getOriginalFilename()))) {
            IOUtils.copy(is, os);
        } catch (IOException e) {
            logger.error("File error when upload {}",file.getName());
            e.printStackTrace();
        }
    }


    /**
     * 查询文件信息
     * 查询出file数组后 调用到私有方法
     * @param page 页号 从第一页开始
     * @param limit 分页长度
     * @return 包装好的信息
     */
    public List<FileItem> listFile(Integer page, Integer limit) {
        File[] files = uploadDir().listFiles();
        return listFile(files,page,limit);
    }

    /**
     * 上传文件
     * 将文件名拼接成 {homeworkId}_{name} 保存
     * @param homeworkId 属于作业的id
     * @param file 文件
     * @return 是否成功
     */
    public boolean upload(String homeworkId, @NotNull MultipartFile file) {
        String filename = homeworkId + "_" + file.getOriginalFilename();
        try (InputStream is = file.getInputStream();
             OutputStream os = new FileOutputStream(new File(uploadDir(), filename))) {
            IOUtils.copy(is, os);
        } catch (IOException e) {
            logger.error("File error when upload {}",file.getName());
            e.printStackTrace();
            return false;
        }
        return true;
    }
    /**
     * 单文件直接下载
     * @param response 响应
     * @param filename 文件名
     * @return 是否成功
     */
    public boolean download(HttpServletResponse response, String filename) {
        return download(response, new File(uploadDirectory+'/'+filename));
    }

    /**
     * 下载多文件, 先打包 在下载 然后删除打包文件
     * @param response 响应
     * @param filename 文件名前缀
     * @param isZip zip文件 必定为true
     * @return 是否成功
     */
    public boolean download(HttpServletResponse response,String filename, boolean isZip) {
        String resname = filename;
        assert isZip;
        if (StringUtils.isEmpty(filename)) {
            resname = "upload";
        }
        File zip = fileToZip(resname,filename);
        if (zip == null || !zip.exists()) {
            return false;
        }
        boolean status = download(response,zip);
        zip.deleteOnExit();
        zip.delete();
        return status;
    }

    /**
     * 先将文件打包成zip
     * 在将zip邮寄出去
     * 删除zip
     * @param filename 文件名
     * @param address 邮件地址
     * @return 是否成功
     */
    public boolean emailZip(String filename, String address) {
        String resname = filename;
        if (StringUtils.isEmpty(filename)) {
            resname = "upload";
        }
        File zip = fileToZip(resname,filename);
        if (zip == null || !zip.exists()) {
            return false;
        }
        // 发送文件邮件
        mailUtils.sendMail(1,zip.getPath(),address);
        zip.deleteOnExit();
        zip.delete();
        return true;
    }



    /**
     * 用IOUtils的流拷贝下载文件
     * @param response 响应
     * @param file 文件
     * @return 是否成功
     */
    private boolean download(HttpServletResponse response, @NotNull File file) {
        // 貌似没必要 前端请求的bolb
//        setFileDownloadHeader(response, file.getName());

        try (InputStream is = new FileInputStream(file);
             OutputStream os = response.getOutputStream()) {
            IOUtils.copy(is,os);
            return true;
        }catch (IOException e) {
            logger.error("File error when download {}",file.getName());
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 根据文件后缀返回文件类型
     * directory -> dir
     * avi,mp4,mp3 -> audio
     * jpg,png,gif -> image
     * * -> document
     * @param file 文件
     * @return 文件类型
     */
    @NotNull
    private String getFileType(@NotNull File file) {
        if (file.isDirectory()) {
            return "dir";
        }
        String name = file.getName();
        // 没有.
        if (name.indexOf('.') == -1) {
            return "document";
        }
        name = name.substring(name.lastIndexOf('.')+1);
        name = name.toLowerCase();
        switch (name) {
            // 因为没有音频图标 音频视频 都算视频了
            case "avi":
            case "mp4":
            case "mp3":
                return "audio";
            case "jpg":
            case "png":
            case "gif":
                return "image";
            default:
                return "document";
        }
    }
    /**
     * 获取上传文件夹
     * 不存在则会尝试创建
     * @return 上传文件夹
     */
    @NotNull
    private File uploadDir() {
        File dir = new File(uploadDirectory);
        if (!dir.exists()) {
            dir.mkdir();
        }
        return dir;
    }

    @NotNull
    private File cloudDir(String path) {
        File dir = new File(cloudDirectory+path);
        if (!dir.exists()) {
            // 把父文件夹也创建了
            dir.mkdirs();
        }
        return dir;
    }

    /**
     * 将 指定前缀的文件打包成zip
     * @param dirName 打包后的文件名
     * @param prefix 前缀
     * @return zip文件
     */
    @Nullable
    private File fileToZip(String dirName, String prefix) {
        BufferedInputStream bis = null;
        File dir = uploadDir();
        File zipFile = new File(dir, dirName+".zip");
        // zip文件已经存在 直接返回
        if (zipFile.exists()) {
            logger.info("file {} already exist.",zipFile.getPath());
            return zipFile;
        }

        // 经过 前缀过滤后的文件数组
        File[] sourceFiles = dir.listFiles(file ->{
//            if (file.isDirectory()){
//                return true;
//            }
            return StringUtils.isEmpty(prefix) || file.getName().toLowerCase().startsWith(prefix);
        });

        // 文件数组为空 返回一个空指针
        if (null == sourceFiles || sourceFiles.length < 1) {
            logger.warn("dir {} has not file in it",uploadDirectory);
            return null;
        }

        // 开始拷贝
        try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))){
            byte[] bufs = new byte[IO_SIZE];
            for (File file : sourceFiles) {
                // 创建zip实体,并添加进压缩包
                zos.putNextEntry(new ZipEntry(file.getName()));
                // 读取待压缩的文件并写进压缩包里
                bis = new BufferedInputStream(new FileInputStream(file), IO_SIZE);

                int read ;
                while ((read = bis.read(bufs, 0, IO_SIZE)) != -1) {
                    zos.write(bufs, 0, read);
                }
            }

            // 没有报错
            return zipFile;
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭文件流
            try {
                if(null != bis){
                    bis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 报错后返回空指针
        return null;
    }

    /**
     * 设置让浏览器弹出下载对话框的Header.
     *
     * @param fileName 下载后的文件名.
     */
    private void setFileDownloadHeader(@NotNull HttpServletResponse response, @NotNull String fileName) {
        try {
            //中文文件名支持
            response.setContentType("application/x-msdownload");
            String encodedfileName = new String(fileName.getBytes("GBK"), "ISO8859-1");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedfileName + "\"");
        } catch (UnsupportedEncodingException e) {
        }
    }

    public List<FileItem> saveDelete(Integer page, Integer limit, @NotNull String[] list) {
        File dir = uploadDir();
        for ( String item : list) {
            File file = new File(dir,item);
            // 这里貌似也不行。。 估计是权限问题？
            file.deleteOnExit();
            file.delete();
        }
        return listFile(dir.listFiles(),page,limit);
    }
}
